﻿//EmulationTrader.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Testing
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;
	using System.Threading;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.ComponentModel;
	using Ecng.Interop;

	using StockSharp.Logging;
	using StockSharp.BusinessEntities;
	using StockSharp.Algo.Storages;
	using StockSharp.Algo.Strategies;
	using StockSharp.Algo.Candles;

	/// <summary>
	/// Эмуляционный шлюз. Использует исторические данные и/или случайно сгенерированные.
	/// </summary>
	public class EmulationTrader : BaseTrader, IEmulationTrader
	{
		private readonly IEnumerable<Portfolio> _portfolios;
		private readonly IEnumerable<Security> _securities;

		private Thread _emulationThread;
		private Thread _loadingThread;

		private readonly object _suspendLock = new object();

		private EmulationStates _state;
		private DateTime _startDate;
		private DateTime _stopDate;
		private DateTime _currentTime;

		//private DateTime _lastHolesCleanup;

		private readonly IDictionary<Portfolio, decimal> _initialMoney;

		private readonly CachedSynchronizedDictionary<Security, MarketDepthGenerator> _depthGenerators = new CachedSynchronizedDictionary<Security, MarketDepthGenerator>();
		private readonly CachedSynchronizedDictionary<Security, TradeGenerator> _tradeGenerators = new CachedSynchronizedDictionary<Security, TradeGenerator>();
		private readonly CachedSynchronizedDictionary<Security, OrderLogGenerator> _orderLogGenerators = new CachedSynchronizedDictionary<Security, OrderLogGenerator>();

		private readonly SynchronizedDictionary<Security, OrderLogMarketDepthBuilder> _orderLogBuilders = new SynchronizedDictionary<Security, OrderLogMarketDepthBuilder>();
		private readonly SynchronizedSet<Security> _orderLogTrades = new SynchronizedSet<Security>();

		private sealed class DeferredActionsManager
		{
			private readonly EmulationTrader _emulationTrader;
			private readonly SynchronizedDictionary<MarketDataTypes, Action> _deferredActions = new SynchronizedDictionary<MarketDataTypes, Action>();
			private DateTime _lastFlushTime;
			public TimeSpan Interval { get; set; }

			public DeferredActionsManager(EmulationTrader emulationTrader)
			{
				_emulationTrader = emulationTrader;
				Interval = TimeSpan.Zero;
			}

			public void Reset()
			{
				_lastFlushTime = default(DateTime);
			}

			public void DeferrAction(Action update, MarketDataTypes dataType)
			{
				_deferredActions[dataType] = update;
				TryFlush();
			}

			public void TryFlush()
			{
				var now = _emulationTrader.MarketEmulator.CurrentTime;

				if (now.Subtract(_lastFlushTime) < Interval)
					return;

				foreach (var a in _deferredActions.CopyAndClear().Select(kv => kv.Value))
				{
					try
					{
						a();
					}
					catch (Exception e)
					{
						e.LogError();
					}
				}

				_lastFlushTime = now;
			}
		}

		/// <summary>
		/// Отложенные действия.
		/// </summary>
		private DeferredActionsManager DeferredActions { get; set; }

		/// <summary>
		/// Создать <see cref="EmulationTrader"/>.
		/// </summary>
		/// <param name="securities">Инструменты, которые будут переданы через событие <see cref="ITrader.NewSecurities"/>.</param>
		/// <param name="portfolios">Портфели, которые будут переданы через событие <see cref="ITrader.NewPortfolios"/>.</param>
		public EmulationTrader(IEnumerable<Security> securities, IEnumerable<Portfolio> portfolios)
			: base(Platforms.AnyCPU)
		{
			if (securities == null)
				throw new ArgumentNullException("securities");

			if (portfolios == null)
				throw new ArgumentNullException("portfolios");

			_securities = securities;
			_portfolios = portfolios;

			_initialMoney = _portfolios.ToDictionary(pf => pf, pf => pf.CurrentValue);

			MarketEmulator = new MarketEmulator(this);
			MarketTimeChangedInterval = TimeSpan.FromSeconds(1);

			DeferredActions = new DeferredActionsManager(this);

			State = EmulationStates.Stopped;
		}

		/// <summary>
		/// Создать <see cref="EmulationTrader"/>.
		/// </summary>
		/// <param name="securities">Инструменты, с которыми будет вестись работа.</param>
		/// <param name="portfolios">Портфели, с которыми будет вестись работа.</param>
		/// <param name="storageRegistry">Хранилище данных.</param>
		public EmulationTrader(IEnumerable<Security> securities, IEnumerable<Portfolio> portfolios, IStorageRegistry storageRegistry)
			: this(securities, portfolios)
		{
			StorageRegistry = storageRegistry;
		}

		/// <summary>
		/// Период аггрегации изменений инструмента. Значение TimeSpan.Zero соответствует отсутствию аггрегации.
		/// При большем периоде тестирование будет идти быстрее, но обновления полей Security будет с задержкой.
		/// Значение по умолчанию TimeSpan.Zero.
		/// </summary>
		public TimeSpan SecurityChangedInterval
		{
			get { return DeferredActions.Interval; }
			set { DeferredActions.Interval = value; }
		}

		/// <summary>
		/// Число загруженных событий.
		/// </summary>
		public int LoadedEventCount { get; private set; }

		/// <summary>
		/// Число обработанных событий.
		/// </summary>
		public int ProcessedEventCount { get; private set; }

		/// <summary>
		/// Состояние эмулятора.
		/// </summary>
		public EmulationStates State
		{
			get { return _state; }
			private set
			{
				if (_state == value)
					return;

				var oldState = _state;
				_state = value;
				StateChanged.SafeInvoke(oldState, _state);
			}
		}


		/// <summary>
		/// Событие о изменении состояния эмулятора <see cref="State"/>.
		/// </summary>
		public event Action<EmulationStates, EmulationStates> StateChanged;

		private IStorageRegistry _storageRegistry;

		/// <summary>
		/// Хранилище данных.
		/// </summary>
		public IStorageRegistry StorageRegistry
		{
			get { return _storageRegistry; }
			set
			{
				_storageRegistry = value;

				if (value != null)
					Drive = value.DefaultDrive;
			}
		}

		private IMarketDataDrive _drive;

		/// <summary>
		/// Хранилище, которое используется по-умолчанию. По умолчанию используется <see cref="IStorageRegistry.DefaultDrive"/>.
		/// </summary>
		public IMarketDataDrive Drive
		{
			get { return _drive; }
			set
			{
				if (value == null && StorageRegistry != null)
					throw new ArgumentNullException();

				_drive = value;
			}
		}

		private IMarketEmulator _marketEmulator;

		/// <summary>
		/// Эмулятор биржи.
		/// </summary>
		public IMarketEmulator MarketEmulator
		{
			get { return _marketEmulator; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (_marketEmulator != null)
					DisposeEmulator();

				_marketEmulator = value;

				_marketEmulator.NewOrder += OnNewOrder;
				_marketEmulator.OrderChanged += OnOrderChanged;
				_marketEmulator.OrderFailed += OnOrderFailed;
				_marketEmulator.NewMyTrades += OnNewMyTrades;
				_marketEmulator.ProcessDataError += RaiseProcessDataError;
				_marketEmulator.NewTrades += OnNewTrades;
				_marketEmulator.MarketDepthsChanged += OnDepthsChanged;
				_marketEmulator.MarketTimeChanged += OnMarketTimeChanged;
				_marketEmulator.NewOrderLogItems += OnOrderLogItems;
				_marketEmulator.MessageReceived += OnEmulatorRaiseEvents;
			}
		}

		private readonly BlockingQueue<Message> _historicalQueue = new BlockingQueue<Message> { MaxSize = 10000 };

		/// <summary>
		/// Очередь исторических данных.
		/// </summary>
		public BlockingQueue<Message> HistoricalQueue
		{
			get { return _historicalQueue; }
		}

		/// <summary>
		/// Поддерживается ли перерегистрация заявок через метод <see cref="ITrader.ReRegisterOrder(StockSharp.BusinessEntities.Order,StockSharp.BusinessEntities.Order)"/>
		/// в виде одной транзакции.
		/// </summary>
		public override bool IsSupportAtomicReRegister
		{
			get
			{
				return MarketEmulator.Settings.IsSupportAtomicReRegister;
			}
		}

		private readonly Dictionary<Strategy, IMarketDataStorage<OrderTraceItem>> _orderTraces = new Dictionary<Strategy, IMarketDataStorage<OrderTraceItem>>();

		/// <summary>
		/// Хранилище изменений заявок стратегий.
		/// </summary>
		public IDictionary<Strategy, IMarketDataStorage<OrderTraceItem>> OrderTraces
		{
			get { return _orderTraces; }
		}

		private int _postTradeMarketTimeChangedCount = 2;

		/// <summary>
		/// Количество вызовов события <see cref="ITrader.MarketTimeChanged"/> после окончания торгов. По-умолчанию равно 2.
		/// </summary>
		/// <remarks>
		/// Необходимо для активации пост-трейд правил (правила, которые опираются на события, происходящие после окончания торгов).
		/// </remarks>
		public int PostTradeMarketTimeChangedCount
		{
			get { return _postTradeMarketTimeChangedCount; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException();

				_postTradeMarketTimeChangedCount = value;
			}
		}

		/// <summary>
		/// Закончил ли эмулятор свою работу по причине окончания данных или он был прерван через метод <see cref="Stop"/>.
		/// </summary>
		public bool IsFinished { get; private set; }

		/// <summary>
		/// Загружать ли и использовать ли стаканы. 
		/// </summary>
		public bool UseMarketDepth
		{
			/* для обратной совместимости нужна, но не более  */
			get { return MarketEmulator.Settings.UseMarketDepth; }
			set { MarketEmulator.Settings.UseMarketDepth = value; }
		}

		/// <summary>
		/// Загружать ли и использовать ли ордерлог. 
		/// </summary>
		public bool UseOrderLog
		{
			get { return MarketEmulator.Settings.UseOrderLog; }
			set { MarketEmulator.Settings.UseOrderLog = value; }
		}

		private TimeSpan _useCandlesTimeFrame;

		/// <summary>
		/// Использовать свечи с заданным тайм-фреймом. Если тайм-фрейм равен <see cref="TimeSpan.Zero"/>, свечи не используются.
		/// </summary>
		public TimeSpan UseCandlesTimeFrame
		{
			get { return _useCandlesTimeFrame; }
			set
			{
				if (value < TimeSpan.Zero)
					throw new ArgumentOutOfRangeException("value", value, "Тайм-фрейм не может быть отрицательным.");

				_useCandlesTimeFrame = value;
			}
		}

		/// <summary>
		/// Использовать ли изменения инструментов.
		/// </summary>
		public bool UseSecurityChanges { get; set; }

		/// <summary>
		/// Начать экспорт события об изменении времени.
		/// </summary>
		protected override void StartMarketTimeExport()
		{
		}

		/// <summary>
		/// Текстовое описание шлюза.
		/// </summary>
		public override string DisplayName
		{
			get { return "Эмулятор на истории"; }
		}

		/// <summary>
		/// Получить биржевое время.
		/// </summary>
		/// <param name="exchange">Биржа.</param>
		/// <returns>Биржевое время.</returns>
		public override DateTime GetMarketTime(Exchange exchange)
		{
			if (exchange == null)
				throw new ArgumentNullException("exchange");

			return TimeZoneInfo.ConvertTime(MarketEmulator.CurrentTime, TimeZoneInfo.Local, exchange.TimeZoneInfo);
		}

		/// <summary>
		/// Подключиться к торговой системе.
		/// </summary>
		protected override void OnConnect()
		{
			// если эмуляция остановлена, подправляем MarketTime на начальный, чтобы обработчики 
			// по-возможности отработали корректно
			if (State == EmulationStates.Stopped)
				MarketEmulator.Reset(_startDate);

			base.OnConnect();
		}

		/// <summary>
		/// Запустить экспорт данных из торговой системы в программу (получение портфелей, инструментов, заявок и т.д.).
		/// </summary>
		public override void StartExport()
		{
			base.StartExport();

			// если эмуляция остановлена, подправляем MarketTime на начальный, чтобы обработчики 
			// по-возможности отработали корректно
			if (State == EmulationStates.Stopped)
				MarketEmulator.Reset(_startDate);

			ProcessEvents(() => _securities.ForEach(security =>
				GetSecurity(security.Id,
					id =>
					{
						//if (security.Trader != null)
						//	throw new ArgumentException("Переданный инструмент {0} уже имеет ассоциацию с другим шлюзом {1}.".Put(security.Id, security.Trader));

						security.Trader = this;
						return security;
					},
					s1 => false)));

			ProcessEvents(() => _portfolios.ForEach(portfolio =>
				GetPortfolio(portfolio.Name,
					name =>
					{
						//if (portfolio.Trader != null)
						//	throw new ArgumentException("Переданный портфель {0} уже имеет ассоциацию с другим шлюзом {1}.".Put(portfolio.Name, portfolio.Trader));

						portfolio.Trader = this;
						return portfolio;
					},
					p1 => false)));
		}

		private bool CheckState(params EmulationStates[] states)
		{
			return states.Contains(State);

			//throw new InvalidOperationException("Невозможно выполнить операцию так как текущее состояние {0}.".Put(State));
		}

		/// <summary>
		/// Текущее время, которое будет передано в <see cref="LogMessage.Time"/>.
		/// </summary>
		public override DateTime CurrentTime
		{
			get { return MarketEmulator.CurrentTime; }
		}

		/// <summary>
		/// Начать эмуляцию.
		/// </summary>
		/// <param name="startDate">Дата в истории, с которой необходимо начать эмуляцию.</param>
		/// <param name="stopDate">Дата в истории, на которой необходимо закончить эмуляцию (дата включается).</param>
		public void Start(DateTime startDate, DateTime stopDate)
		{
			if (stopDate < startDate)
				throw new ArgumentOutOfRangeException("startDate", startDate, "Дата начала {0} больше даты окончания {1}.".Put(startDate, stopDate));

			//f (stopDate == startDate)
			//throw new ArgumentOutOfRangeException("startDate", startDate, "Дата начала {0} равна дате окончания.".Put(startDate));

			if (!CheckState(EmulationStates.Stopped))
				throw new InvalidOperationException("Нельзя запустить тестирование так как шлюз находится в состоянии {0}.".Put(State));

			State = EmulationStates.Starting;

			_startDate = startDate;
			_stopDate = stopDate;


			_historicalQueue.Clear();
			_historicalQueue.Open();

			LoadedEventCount = ProcessedEventCount = 0;

			ClearPositions();
			Portfolios.ForEach(pf => pf.CurrentValue = _initialMoney[pf]);

			if (StorageRegistry == null)
			{
				foreach (var security in RegisteredTrades)
				{
					if (!_tradeGenerators.ContainsKey(security))
						throw new InvalidOperationException("Для инструмента {0} не задан генератор сделок.".Put(security.Id));
				}

				foreach (var security in RegisteredMarketDepths)
				{
					if (!_depthGenerators.ContainsKey(security))
						throw new InvalidOperationException("Для инструмента {0} не задан генератор стаканов.".Put(security.Id));
				}

				foreach (var security in RegisteredOrderLogs)
				{
					if (!_orderLogGenerators.ContainsKey(security))
						throw new InvalidOperationException("Для инструмента {0} не задан генератор лога заявок.".Put(security.Id));
				}
			}

			_depthGenerators.CachedValues.ForEach(d => d.Init());
			_tradeGenerators.CachedValues.ForEach(d => d.Init());
			_orderLogGenerators.CachedValues.ForEach(d => d.Init());

			//_lastHolesCleanup = DateTime.MinValue;
			IsFinished = false;

			_orderLogBuilders.Clear();

			if (CreateDepthFromOrdersLog)
			{
				foreach (var security in RegisteredMarketDepths.Intersect(RegisteredOrderLogs))
				{
					_orderLogBuilders.Add(security, new OrderLogMarketDepthBuilder(GetMarketDepth(security)));
				}
			}

			_orderLogTrades.Clear();

			if (CreateTradesFromOrdersLog)
				_orderLogTrades.AddRange(RegisteredTrades.Intersect(RegisteredOrderLogs));

			if (_emulationThread != null)
				throw new InvalidOperationException("Поток эмуляции не остановился с предыдущего запуска.");

			DeferredActions.Reset();

			// подписчики StateChanged запускают стратегии, которые интересуются MarketTime в OnRunning
			MarketEmulator.Reset(_startDate);
			MarketEmulator.Start();
			State = EmulationStates.Started;


			_emulationThread = ThreadingHelper
				.Thread(OnProcess)
				.Name("EmulationTrader. Emulation thread")
				.Launch();

			if (_loadingThread != null)
				throw new InvalidOperationException("Поток загрузки данных не остановились с предыдущего запуска.");

			_loadingThread = ThreadingHelper
					.Thread(OnLoad)
					.Name("EmulationTrader. Loader thread")
					.Launch();
		}

		/// <summary>
		/// Остановить эмуляцию.
		/// </summary>
		public void Stop()
		{
			if (!CheckState(EmulationStates.Started, EmulationStates.Suspended))
				return;

			Debug.WriteLine("EmulationTrader.Stop() >" + DateTime.Now);

			State = EmulationStates.Stopping;

			_historicalQueue.Close();

			lock (_suspendLock)
				Monitor.PulseAll(_suspendLock);

			Debug.WriteLine("EmulationTrader.Stop() <" + DateTime.Now);
		}

		/// <summary>
		/// Приостановить эмуляцию.
		/// </summary>
		public void Suspend()
		{
			if (!CheckState(EmulationStates.Started))
				return;

			State = EmulationStates.Suspending;
		}

		/// <summary>
		/// Возобновить эмуляцию.
		/// </summary>
		public void Resume()
		{
			if (!CheckState(EmulationStates.Suspended))
				return;

			State = EmulationStates.Started;

			lock (_suspendLock)
				Monitor.PulseAll(_suspendLock);
		}

		/// <summary>
		/// Реализация потока тестирования.
		/// </summary>
		private void OnProcess()
		{
			try
			{

				while (State != EmulationStates.Stopping)
				{
					WaitIfSuspended();

					Message e;
					if (!_historicalQueue.TryDequeue(out e))
						break;

					//System.Console.WriteLine(e.ToString());

					if (e is LastMessage)
					{
						if (State == EmulationStates.Started)
							IsFinished = true;

						State = EmulationStates.Stopping;
						break;
					}

					if (e is OrderTraceMessage)
					{
						var ot = e as OrderTraceMessage;
						OnOrderTrace(ot.Strategy, ot.Events);
					}
					else if (e is Message<SecurityDataInfo>)
					{
						OnSecurityDataInfo((e as Message<SecurityDataInfo>).Events);
					}
					else
					{
						EmulateHistoryMessage(e);

						//if (e is TimeMessage)
						//{
						//	ProcessMarketDepthHoles();
						//}
					}

					ProcessedEventCount++;
				}
			}
			catch (Exception ex)
			{
				RaiseProcessDataError(ex);
			}

			MarketEmulator.Stop();

			_historicalQueue.Close(); // чтобы остановить Loader Threads

			_emulationThread = null;

			Debug.WriteLine(Thread.CurrentThread.Name + " done " + DateTime.Now);
			State = EmulationStates.Stopped;
		}

		private void OnSecurityDataInfo(IEnumerable<SecurityDataInfo> sdi)
		{
			foreach (var info in sdi)
			{
				_marketEmulator.GetSettings(info.Security).UseOrderLog = info.Types.Contains(MarketDataTypes.OrderLog);
			}
		}

		private void EmulateHistoryMessage(Message message)
		{
			MarketEmulator.Emulate(message);
		}

		private void OnEmulatorRaiseEvents(Message e)
		{
			if (e is SecurityChangeMessage)
			{
				OnSecurityChange(e as SecurityChangeMessage);
			}
		}

		private Order SafeGetOrderByTransactionId(OrderTraceItem item)
		{
			var order = GetOrderByTransactionId(item.Order.TransactionId);

			if (order == null)
				throw new InvalidOperationException("Заявка с номером транзакции {0} не найдена.".Put(item.Order.TransactionId));

			return order;
		}

		private void OnOrderTrace(Strategy strategy, IEnumerable<OrderTraceItem> items)
		{
			foreach (var item in items)
			{
				Order order;

				//Trace.WriteLine(item.Time.ToString("yyyyMMdd HHmmss.fff") + ":" + item.Action+":"+item.Order.TransactionId);

				switch (item.Action)
				{
					case OrderTraceActions.Registering:
						order = item.Order;
						order.Portfolio = strategy.Portfolio;
						order.Security = strategy.Security;
						strategy.RegisterOrder(item.Order);
						break;
					case OrderTraceActions.Registered:
						order = GetNewOrder(item.Order);
						OnNewOrder(order);
						break;
					case OrderTraceActions.Failed:
						order = SafeGetOrderByTransactionId(item);
						UpdateOrder(order, item.Order);

						RaiseOrderFailed(new OrderFail
						{
							Error = item.Fail.Error,
							Order = order
						});
						break;
					case OrderTraceActions.Changed:
						order = SafeGetOrderByTransactionId(item);
						UpdateOrder(order, item.Order);
						RaiseOrderChanged(order);
						break;
					case OrderTraceActions.NewMyTrade:
						order = SafeGetOrderByTransactionId(item);
						var myTrade = new MyTrade
						{
							Order = order,
							Trade = item.Trade
						};
						RaiseNewMyTrades(new[] { myTrade });
						break;

					case OrderTraceActions.Canceling:
						order = SafeGetOrderByTransactionId(item);
						CancelOrder(order);
						break;
				}
			}
		}

		private void OnSecurityChange(SecurityChangeMessage msg)
		{
			var changes = msg.Events;
			var security = changes.First().Security;

			security.ApplyChanges(changes);

			RaiseSecurityChanged(security);
		}

		private void UpdateOrderlogBuilders(IEnumerable<OrderLogItem> items)
		{
			var security = items.First().Order.Security;

			var builder = _orderLogBuilders.TryGetValue(security);

			if (builder != null)
			{
				foreach (var item in items)
				{
					if (builder.Update(item))
						RaiseMarketDepthChanged(builder.Depth);
				}
			}

			if (_orderLogTrades.Contains(security))
			{
				foreach (var item in items)
				{
					var trade = item.Trade;

					if (trade != null)
						RaiseNewTrade(trade);
				}
			}
		}

		private void WaitIfSuspended()
		{
			if (State == EmulationStates.Suspending)
				State = EmulationStates.Suspended;

			if (State != EmulationStates.Suspended)
				return;

			lock (_suspendLock)
			{
				if (State == EmulationStates.Suspended)
					Monitor.Wait(_suspendLock);
			}
		}

		private abstract class EventReader : SimpleEnumerator<Message>
		{
			public DateTime CurrentTime { get; protected set; }

			public Func<Message, IEnumerable<Message>> PostEvent { get; set; }
		}

		private sealed class MarketDataEnumeratorImpl<T> : IMarketDataEnumerator<T>
		{
			private readonly IEnumerator<T> _inner;

			public MarketDataEnumeratorImpl(IEnumerator<T> inner)
			{
				if (inner == null)
					throw new ArgumentNullException("inner");

				_inner = inner;
			}

			public T Current
			{
				get { return _inner.Current; }
			}

			public T Previous
			{
				get { return default(T); }
			}

			public T Delta
			{
				get { return default(T); }
			}

			public void Dispose()
			{
				_inner.Dispose();
			}

			object System.Collections.IEnumerator.Current
			{
				get { return Current; }
			}

			public bool MoveNext()
			{
				return _inner.MoveNext();
			}

			public void Reset()
			{
				_inner.Reset();
			}
		}

		private sealed class EventReader<T> : EventReader
		{
			private readonly IMarketDataEnumerator<T> _enumerator;
			private Message<T> _message;
			private readonly Func<T, DateTime> _getTime;
			private readonly Func<T, Message<T>> _createEvent;
			private bool _hasMore;

			public EventReader(IMarketDataStorage<T> storage, DateTime date, Func<T, DateTime> getTime, Func<T, Message<T>> createEvent)
				: this(storage.Load(date), getTime, createEvent)
			{
			}

			public EventReader(IEnumerable<T> enumerable, Func<T, DateTime> getTime, Func<T, Message<T>> createEvent)
			{
				if (enumerable == null)
					throw new ArgumentNullException("enumerable");

				var enu = enumerable.GetEnumerator();
				_enumerator = enu as IMarketDataEnumerator<T> ?? new MarketDataEnumeratorImpl<T>(enu);

				_getTime = getTime;
				_createEvent = createEvent;
				_hasMore = _enumerator.MoveNext();
			}

			public override bool MoveNext()
			{
				if (!_hasMore)
					return false;

				Current = _message = _createEvent(_enumerator.Current);
				CurrentTime = _message.Time;

				var hasDelta = !_enumerator.Delta.IsNull();

				if (hasDelta)
					_message.Deltas = new List<T>(new[] { _enumerator.Delta });

				do
				{
					_hasMore = _enumerator.MoveNext();

					if (!_hasMore)
						break;

					var current = _enumerator.Current;

					if (_message.Time != _getTime(current))
						break;

					_message.Events.Add(current);

					if (hasDelta)
						_message.Deltas.Add(_enumerator.Delta);
				}
				while (true);

				return true;
			}

			protected override void DisposeManaged()
			{
				_enumerator.Dispose();
				base.DisposeManaged();
			}
		}

		private sealed class CandleAsTradeReader : EventReader
		{
			private readonly EventReader<Candle> _candleReader;

			private enum Stage
			{
				Open = 0,
				//High = 1,
				//Low = 2,
				Close = 3
			}

			private Stage _stage = Stage.Open;

			public CandleAsTradeReader(IMarketDataStorage<Candle> storage, DateTime date)
			{
				_candleReader = new EventReader<Candle>(storage, date, c => c.OpenTime, c => new Message<Candle>(c.OpenTime, new[] { c }));
			}

			public override bool MoveNext()
			{
				if (_stage == Stage.Open)
				{
					if (!_candleReader.MoveNext())
						return false;
				}

				var candleMsg = (Message<Candle>)_candleReader.Current;
				var candle = candleMsg.Events[0];
				var vol = candle.TotalVolume / 4;
				var isUptrend = candle.ClosePrice >= candle.OpenPrice;

				if (_stage == Stage.Open)
				{
					CurrentTime = candle.OpenTime;
					Current = new TradeMessage(CurrentTime,
						new[]
						{
							new Trade
							{
								Time = candle.OpenTime,
								Price = candle.OpenPrice,
								Volume = vol,
								Security = candle.Security,
								OrderDirection = isUptrend ? OrderDirections.Buy : OrderDirections.Sell
							}
						});

					_stage = Stage.Close;
				}
				else
				{
					CurrentTime = candle.CloseTime;
					var h = new Trade { Time = candle.CloseTime, Price = candle.HighPrice, Volume = vol, Security = candle.Security, OrderDirection = OrderDirections.Buy };
					var l = new Trade { Time = candle.CloseTime, Price = candle.LowPrice, Volume = vol, Security = candle.Security, OrderDirection = OrderDirections.Sell };
					var c = new Trade { Time = candle.CloseTime, Price = candle.ClosePrice, Volume = vol, Security = candle.Security, OrderDirection = isUptrend ? OrderDirections.Buy : OrderDirections.Sell };

					var trades = new[] { h, l, c };

					if (candle.ClosePrice > candle.OpenPrice)
						trades = new[] { l, h, c };

					Current = new TradeMessage(CurrentTime, trades);
					_stage = Stage.Open;
				}

				return true;
			}
		}

		private sealed class EventComparer : IComparer<EventReader>
		{
			public int Compare(EventReader x, EventReader y)
			{
				return x.CurrentTime.CompareTo(y.CurrentTime);
			}
		}

		private static int FindIndex(EventReader[] readers, DateTime time, int start, int stop)
		{
			var startTime = readers[start].CurrentTime;

			if (time <= startTime)
				return start - 1;

			var stopTime = readers[stop].CurrentTime;

			if (time >= stopTime)
				return stop + 1;

			while (stop - start > 1)
			{
				var mid = (start + stop) >> 1;
				var midTime = readers[mid].CurrentTime;

				if (time > midTime)
					start = mid; // time>time[start]
				else if (time < midTime)
					stop = mid; // time<time[stop]
				else
					return mid; // time=time[id]
			}

			if (!(readers[start].CurrentTime < time && time < readers[stop].CurrentTime))
				throw new InvalidOperationException();

			return stop;
		}

		private void InitOrderLogBuilders(DateTime loadDate)
		{
			if (StorageRegistry == null || !UseMarketDepth)
				return;

			foreach (var security in RegisteredMarketDepths)
			{
				var builder = _orderLogBuilders.TryGetValue(security);

				if (builder == null)
					continue;

				// стакан из ОЛ строиться начиная с 18.45 предыдущей торговой сессии
				var olDate = loadDate.Date;

				do
				{
					olDate -= TimeSpan.FromDays(1);
				}
				while (!ExchangeBoard.Forts.WorkingTime.IsTradeDate(olDate));

				olDate += new TimeSpan(18, 45, 0);

				foreach (var item in StorageRegistry.GetOrderLogStorage(security, Drive).Load(olDate, loadDate - TimeSpan.FromTicks(1)))
				{
					builder.Update(item);
				}
			}
		}

		private void AddCandlesReaders(List<EventReader> readers, Dictionary<Security, EventReader> tradesReaders, DateTime loadDate)
		{
			foreach (var security in RegisteredTrades)
			{
				var tradesReader = new CandleAsTradeReader(StorageRegistry.GetCandleStorage(typeof(TimeFrameCandle), security, UseCandlesTimeFrame, Drive), loadDate.Date);

				if (tradesReader.MoveNext())
				{
					readers.Add(tradesReader);
					tradesReaders.Add(security, tradesReader);
				}
			}
		}

		private void AddTradesReaders(List<EventReader> readers, Dictionary<Security, EventReader> tradesReaders, Dictionary<Security, List<MarketDataTypes>> types, DateTime loadDate)
		{
			foreach (var security in RegisteredTrades)
			{
				if (types.SafeAdd(security).Contains(MarketDataTypes.OrderLog))
					continue;

				var tradesReader = new EventReader<Trade>(StorageRegistry.GetTradeStorage(security, Drive), loadDate.Date, t => t.Time, t => new TradeMessage(t.Time, t));

				if (tradesReader.MoveNext())
				{
					readers.Add(tradesReader);
					tradesReaders.Add(security, tradesReader);

					types.SafeAdd(security).Add(MarketDataTypes.Trades);
				}
			}
		}

		private void AddDepthReaders(List<EventReader> readers, Dictionary<Security, EventReader> tradesReaders, Dictionary<Security, List<MarketDataTypes>> types, DateTime loadDate)
		{
			foreach (var security in RegisteredMarketDepths)
			{
				if (types.SafeAdd(security).Contains(MarketDataTypes.OrderLog))
					continue;

				var depthGenerator = _depthGenerators.TryGetValue(security);

				if (depthGenerator == null)
				{
					var depthsReader = new EventReader<MarketDepth>(StorageRegistry.GetMarketDepthStorage(security, Drive), loadDate.Date, t => t.LastChangeTime, t => new DepthMessage(t.LastChangeTime, t));

					if (depthsReader.MoveNext())
					{
						readers.Add(depthsReader);
						types.SafeAdd(security).Add(MarketDataTypes.MarketDepth);
					}
				}
				else
				{
					var tradesReader = tradesReaders.TryGetValue(security);

					if (tradesReader != null)
					{
						tradesReader.PostEvent = e =>
						{
							depthGenerator.UpdateTrades(((TradeMessage)e).Events);
							return new[] { new DepthMessage(e.Time, depthGenerator.Generate(e.Time)) };
						};
					}
				}
			}
		}

		private void AddOrderLogReaders(List<EventReader> readers, Dictionary<Security, List<MarketDataTypes>> types, DateTime loadDate)
		{
			foreach (var security in RegisteredOrderLogs)
			{
				var logReader = new EventReader<OrderLogItem>(StorageRegistry.GetOrderLogStorage(security, Drive), loadDate.Date, t => t.Order.Time, t => new OrderLogMessage(t.Order.Time, t));

				if (Seek(logReader, loadDate))
				{
					readers.Add(logReader);
					types.SafeAdd(security).Add(MarketDataTypes.OrderLog);
				}
			}
		}

		private static IEnumerable<Clearing> GetClearings(Security s, DateTime loadDate)
		{
			return new[]
			{
				new Clearing
				{
					Security = s,
					Time = loadDate.Date + s.ExchangeBoard.ExpiryTime,
					ClearMarketDepth = true
				}
			};
		}

		private void AddClearingReaders(List<EventReader> readers, DateTime loadDate)
		{
			foreach (var security in Securities)
			{
				var clReader = new EventReader<Clearing>(GetClearings(security, loadDate), c => c.Time, c => new ClearingMessage(c.Time, c));

				if (Seek(clReader, loadDate))
					readers.Add(clReader);
			}
		}

		private void AddChangesReaders(List<EventReader> readers, Dictionary<Security, List<MarketDataTypes>> types, DateTime loadDate, bool useChanges)
		{
			foreach (var security in RegisteredSecurities)
			{
				var securityChangesReader = new EventReader<SecurityChange>(StorageRegistry.GetSecurityChangeStorage(security, Drive),
					loadDate.Date, c => c.Time, c => new SecurityChangeMessage(c.Time, c));

				if (useChanges && Seek(securityChangesReader, loadDate))
				{
					readers.Add(securityChangesReader);
					types.SafeAdd(security).Add(MarketDataTypes.Security);
				}
				//else
				//{
				//    var defaults = GetSecurityDefaults(security, loadDate);

				//    securityChangesReader = new EventReader<SecurityChange>(defaults, c => c.Time, c => new SecurityChangeMessage(c.Time, c));

				//    if (Seek(securityChangesReader, loadDate))
				//        readers.Add(securityChangesReader);
				//}
			}
		}

		private static void AddDataAvailReaders(List<EventReader> readers, Dictionary<Security, List<MarketDataTypes>> types, DateTime loadDate)
		{
			foreach (var sec in types.Keys)
			{
				var msg = new SecurityDataInfo
				{
					Security = sec,
					Types = types[sec],
					Time = loadDate
				};

				var rdr = new EventReader<SecurityDataInfo>(new[] { msg }, d => d.Time, d => new Message<SecurityDataInfo>(d.Time, new[] { d }));

				if (Seek(rdr, loadDate))
					readers.Add(rdr);
			}
		}

		private void AddMinStepSizeReaders(List<EventReader> readers, Dictionary<Security, List<MarketDataTypes>> types, DateTime loadDate)
		{
			foreach (var security in types.Keys)
			{
				if (types[security].Count == 0)
					continue;

				var type = types[security].First();

				IMarketDataStorage storage;
				switch (type)
				{
					case MarketDataTypes.OrderLog:
						storage = StorageRegistry.GetOrderLogStorage(security, Drive);
						break;
					case MarketDataTypes.Trades:
						storage = StorageRegistry.GetTradeStorage(security, Drive);
						break;
					case MarketDataTypes.MarketDepth:
						storage = StorageRegistry.GetMarketDepthStorage(security, Drive);
						break;
					case MarketDataTypes.Security:
						storage = StorageRegistry.GetSecurityChangeStorage(security, Drive);
						break;
					default:
						throw new InvalidOperationException("Неизвестный тип хранилища данных {0}.".Put(type));
				}

				var metaInfo = storage.GetMetaInfo(loadDate);
				if (metaInfo != null)
				{
					var securityChange = new SecurityChange(security, loadDate, SecurityChangeTypes.MinStepSize, metaInfo.MinStepSize);
					var minStepSizeReader = new EventReader<SecurityChange>(new[] { securityChange }, c => c.Time, c => new SecurityChangeMessage(c.Time, c));

					if (Seek(minStepSizeReader, loadDate))
						readers.Add(minStepSizeReader);
				}
			}
		}

		private IEnumerable<Message> GenerationPostEvent(Message e)
		{
			var postEvents = new List<Message>();

			foreach (var tradeGenerator in _tradeGenerators.Values)
			{
				if (!tradeGenerator.IsTimeToGenerate(e.Time))
					continue;

				var trade = tradeGenerator.Generate(e.Time);

				postEvents.Add(new TradeMessage(trade.Time, trade));

				var depthGenerator = _depthGenerators.TryGetValue(tradeGenerator.Security);

				if (depthGenerator == null)
					continue;

				depthGenerator.UpdateTrades(new[] { trade });

				if (depthGenerator.IsTimeToGenerate(e.Time))
					postEvents.Add(new DepthMessage(e.Time, depthGenerator.Generate(e.Time)));
			}

			return postEvents;
		}

		private static bool Seek(EventReader reader, DateTime time)
		{
			while (reader.MoveNext())
			{
				if (reader.CurrentTime >= time)
					return true;
			}

			return false;
		}

		private List<EventReader> GetEventReaders(DateTime loadDate)
		{
			var readers = new List<EventReader>();

			var timeReader = new EventReader<DateTime>(GetTimeLine(loadDate), t => t, t => new TimeMessage(t, t));

			if (Seek(timeReader, loadDate))
				readers.Add(timeReader);

			if (StorageRegistry != null)
			{
				var tradesReaders = new Dictionary<Security, EventReader>();

				var types = new Dictionary<Security, List<MarketDataTypes>>();

				if (UseCandlesTimeFrame != TimeSpan.Zero)
					AddCandlesReaders(readers, tradesReaders, loadDate);
				else
				{
					if (UseOrderLog)
						AddOrderLogReaders(readers, types, loadDate /*, loadDate.Date == _startDate.Date*/);

					// если определенная секьюрити имеет ордерлог, то трейды и стаканы не используем
					AddTradesReaders(readers, tradesReaders, types, loadDate);

					if (UseMarketDepth)
						AddDepthReaders(readers, tradesReaders, types, loadDate);

					AddChangesReaders(readers, types, loadDate, UseSecurityChanges);
				}

				AddDataAvailReaders(readers, types, loadDate);
				AddMinStepSizeReaders(readers, types, loadDate);

				AddClearingReaders(readers, loadDate);
			}
			else
			{
				timeReader.PostEvent = GenerationPostEvent;
			}

			readers.AddRange(from kv in _orderTraces
							 let strategy = kv.Key
							 select new EventReader<OrderTraceItem>(kv.Value, loadDate, t => t.Time, t => new OrderTraceMessage(t.Time, strategy, t))
								 into traceReader
								 where traceReader.MoveNext()
								 select traceReader);

			return readers;
		}

		private void EnqueueEvents(List<EventReader> readers)
		{
			readers.Sort(new EventComparer());

			var readersArray = readers.ToArray();

			var readersCount = readersArray.Length;

			if (readersCount <= 0)
				return;

			while (!_historicalQueue.IsClosed)
			{
				var first = readersArray[0];

				var ev = first.Current;

				LoadedEventCount += ev.EventCount;
				_historicalQueue.Enqueue(ev);

				if (first.PostEvent != null)
				{
					foreach (var postEvent in first.PostEvent(ev))
						_historicalQueue.Enqueue(postEvent);
				}

				if (!first.MoveNext())
				{
					Array.Copy(readersArray, 1, readersArray, 0, readersCount - 1);

					if (--readersCount == 0)
						break;
				}
				else if (readersCount > 1)
				{
					var newIndex = FindIndex(readersArray, first.CurrentTime, 1, readersCount - 1);

					if (newIndex != 0)
					{
						Array.Copy(readersArray, 1, readersArray, 0, newIndex - 1);
						readersArray[newIndex - 1] = first;
					}
				}
			}
		}

		private void OnLoad()
		{
			try
			{
				var loadDate = _startDate;

				InitOrderLogBuilders(loadDate);

				while (loadDate.Date <= _stopDate.Date && !_historicalQueue.IsClosed)
				{
					this.AddInfoLog("Loading {0} Events: {1}", loadDate, LoadedEventCount);

					var readers = GetEventReaders(loadDate);

					EnqueueEvents(readers);

					loadDate = loadDate.Date.AddDays(1);
				}

				_historicalQueue.Enqueue(new LastMessage());
			}
			catch (Exception ex)
			{
				RaiseProcessDataError(ex);
			}

			_loadingThread = null;

			Debug.WriteLine("{0} done.".Put(Thread.CurrentThread.Name));
		}

		private IEnumerable<DateTime> GetTimeLine(DateTime date)
		{
			var timeLine = new List<DateTime>();

			foreach (var board in ExchangeBoards)
			{
				var isWorkingDay = board.WorkingTime.IsTradeDate(date, true);

				if (!isWorkingDay)
					continue;

				var max = TimeSpanHelper.LessOneDay;

				var timeBounds = board.WorkingTime.Times;

				if (timeBounds.Length == 0)
					timeBounds = new[] { new Range<TimeSpan>(TimeSpan.Zero, max) };

				var lastTime = TimeSpan.Zero;

				foreach (var range in timeBounds)
				{
					for (var time = range.Min; time <= range.Max; time += MarketTimeChangedInterval)
					{
						lastTime = time;
						timeLine.Add(date.Date + time);
					}
				}

				for (var i = 0; i < PostTradeMarketTimeChangedCount; i++)
				{
					lastTime += MarketTimeChangedInterval;

					if (lastTime > max)
						break;

					timeLine.Add(date.Date + lastTime);
				}
			}

			timeLine.Sort();
			return timeLine;
		}

		/// <summary>
		/// Зарегистрировать заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, содержащая информацию для регистрации.</param>
		protected override void OnRegisterOrder(Order order)
		{
			if (!order.IsTrace)
				MarketEmulator.RegisterOrder(order);
		}

		/// <summary>
		/// Отменить заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, которую нужно отменять.</param>
		protected override void OnCancelOrder(Order order)
		{
			if (!order.IsTrace)
				MarketEmulator.CancelOrder(order);
		}

		/// <summary>
		/// Перерегистрировать заявку.
		/// </summary>
		/// <param name="oldOrder">Старая заявка.</param>
		/// <param name="newOrder">Новая заявка.</param>
		protected override void OnReRegisterOrder(Order oldOrder, Order newOrder)
		{
			if (newOrder.IsTrace)
				return;

			if (MarketEmulator.Settings.IsSupportAtomicReRegister)
			{
				InitNewOrder(newOrder);
				this.AddOrderInfoLog(newOrder, "ReRegisterOrder", () => ", Old:" + oldOrder);

				MarketEmulator.ReRegisterOrder(oldOrder, newOrder);
			}
			else
			{
				CancelOrder(oldOrder);
				RegisterOrder(newOrder);
			}
		}

		/// <summary>
		/// Зарегистрировать генератор сделок.
		/// </summary>
		/// <param name="generator">Генератор сделок.</param>
		public void RegisterTrades(TradeGenerator generator)
		{
			if (generator == null)
				throw new ArgumentNullException("generator");

			var security = generator.Security;

			_tradeGenerators[security] = generator;
			RegisterTrades(security);
		}

		/// <summary>
		/// Удалить генератор сделок, ранее зарегистрированный через <see cref="RegisterTrades"/>.
		/// </summary>
		/// <param name="generator">Генератор сделок.</param>
		public void UnRegisterTrades(TradeGenerator generator)
		{
			if (generator == null)
				throw new ArgumentNullException("generator");

			var security = generator.Security;

			_tradeGenerators.Remove(security);
			UnRegisterTrades(security);
		}

		/// <summary>
		/// Зарегистрировать генератор стаканов.
		/// </summary>
		/// <param name="generator">Генератор стаканов.</param>
		public void RegisterMarketDepth(MarketDepthGenerator generator)
		{
			if (generator == null)
				throw new ArgumentNullException("generator");

			var security = generator.Security;

			_depthGenerators[security] = generator;
			RegisterMarketDepth(security);
		}

		/// <summary>
		/// Удалить генератор стаканов, ранее зарегистрированный через <see cref="RegisterMarketDepth"/>.
		/// </summary>
		/// <param name="generator">Генератор стаканов.</param>
		public void UnRegisterMarketDepth(MarketDepthGenerator generator)
		{
			if (generator == null)
				throw new ArgumentNullException("generator");

			var security = generator.Security;

			_depthGenerators.Remove(security);
			UnRegisterMarketDepth(security);
		}

		/// <summary>
		/// Зарегистрировать генератор лога заявок.
		/// </summary>
		/// <param name="generator">Генератор лога заявок.</param>
		public void RegisterOrderLog(OrderLogGenerator generator)
		{
			if (generator == null)
				throw new ArgumentNullException("generator");

			var security = generator.Security;

			_orderLogGenerators[security] = generator;
			RegisterOrderLog(security);
		}

		/// <summary>
		/// Удалить генератор лога заявок, ранее зарегистрированный через <see cref="RegisterOrderLog"/>.
		/// </summary>
		/// <param name="generator">Генератор лога заявок.</param>
		public void UnRegisterOrderLog(OrderLogGenerator generator)
		{
			if (generator == null)
				throw new ArgumentNullException("generator");

			var security = generator.Security;

			_orderLogGenerators.Remove(security);
			UnRegisterOrderLog(security);
		}

		private void OnMarketTimeChanged()
		{
			var time = MarketEmulator.CurrentTime;

			if (time == default(DateTime))
				return;

			var diff = time - _currentTime;

			if (diff == TimeSpan.Zero)
				return;

			DeferredActions.TryFlush();

			_currentTime = time;
			RaiseMarketTimeChanged(diff);
		}

		private void OnNewTrades(IEnumerable<Trade> trades)
		{
			RaiseNewTrades(trades);

			DeferredActions.DeferrAction(() =>
			{
				var lastTrade = trades.Last();
				lastTrade.Security.LastTrade = lastTrade;
				RaiseSecurityChanged(lastTrade.Security);
			}, MarketDataTypes.Trades);
		}

		private void OnDepthsChanged(IEnumerable<MarketDepth> depths)
		{
			var depth = depths.Last();
			GetMarketDepth(depth.Security).Update(depth.Bids, depth.Asks, depth.LastChangeTime);

			if (depth.Bids.Length == 0 || depth.Asks.Length == 0)
			{
				this.AddDebugLog("Empty depth {0} {1} {2}", depth.LastChangeTime, depth.BestBid, depth.BestAsk);
			}

			RaiseMarketDepthChanged(depth);
		}

		/// <summary>
		/// Вызвать событие <see cref="ITrader.MarketDepthsChanged"/>.
		/// </summary>
		/// <param name="marketDepth">Стакан, который нужно передать в событие.</param>
		protected override void RaiseMarketDepthChanged(MarketDepth marketDepth)
		{
			if (marketDepth == null)
				throw new ArgumentNullException("marketDepth");

			var security = marketDepth.Security;

			RaiseMarketDepthsChanged(new[] { marketDepth });

			DeferredActions.DeferrAction(() =>
			{
				using (security.BeginUpdate())
				{
					security.BestBid = marketDepth.BestBid.CloneNullable();
					security.BestAsk = marketDepth.BestAsk.CloneNullable();
					security.LastChangeTime = marketDepth.LastChangeTime;
				}
				RaiseSecurityChanged(security);
			}, MarketDataTypes.MarketDepth);
		}

		private void OnOrderLogItems(IEnumerable<OrderLogItem> items)
		{
			RaiseNewOrderLogItems(items);
			UpdateOrderlogBuilders(items);
		}

		private static bool UpdateOrder(Order newOrder, Order state)
		{
			using (newOrder.BeginUpdate())
			{
				newOrder.Balance = state.Balance;
				newOrder.LastChangeTime = state.LastChangeTime;
				newOrder.State = state.State;
			}

			return true;
		}

		private Order GetNewOrder(Order state)
		{
			var newOrder = GetOrderByTransactionId(state.TransactionId);

			if (null == newOrder)
			{
				newOrder = EntityFactory.CreateOrder(state.Security, state.Id);
				newOrder.TransactionId = state.TransactionId;

				newOrder.Price = state.Price;
				newOrder.Volume = state.Volume;
				newOrder.Balance = state.Balance;
			}

			using (newOrder.BeginUpdate())
			{
				newOrder.Id = state.Id;
				newOrder.LastChangeTime = state.LastChangeTime;
				newOrder.Time = state.Time;
				newOrder.Balance = state.Balance;
				newOrder.State = state.State;
			}

			return newOrder;
		}

		private void OnNewOrder(Order state)
		{
			var order = GetOrder(state.Security, state.Id, orderId => GetNewOrder(state), oldOrderId => false);

			UpdatePosition(order.Portfolio, order.Security, pos =>
			{
				pos.BlockedValue += order.GetPosition();
				return true;
			});
		}


		private void OnOrderChanged(Order state)
		{
			GetOrder(state.Security, state.Id,
				orderId => GetNewOrder(state),
				oldOrder => UpdateOrder(oldOrder, state));
		}

		private void OnOrderFailed(OrderFail fail)
		{
			var order = GetOrderByTransactionId(fail.Order.TransactionId);

			if (null == order)
				throw new ArgumentException("Не найдена заявка {0}.".Put(fail.Order.TransactionId), "fail");

			using (order.BeginUpdate())
			{
				order.Id = fail.Order.Id;
				order.Time = fail.Order.Time;
				order.LastChangeTime = fail.Order.LastChangeTime;
				order.Balance = fail.Order.Balance;
				order.State = fail.Order.State;
			}

			RaiseOrderFailed(order, fail.Error);
		}

		private void OnNewMyTrades(IEnumerable<MyTrade> trades)
		{
			foreach (var t in trades)
			{
				var trade = t;

				var security = trade.Order.Security;
				var portfolio = trade.Order.Portfolio;

				AddMyTrade(security, trade.Order.Id, trade.Order.TransactionId, trade.Trade);

				UpdatePosition(portfolio, security, pos =>
				{
					pos.CurrentValue += trade.GetPosition();
					pos.BlockedValue -= trade.GetPosition();
					return true;
				});
			}
		}

		/// <summary>
		/// Начать получать новую информацию по портфелю.
		/// </summary>
		/// <param name="portfolio">Портфель, по которому необходимо начать получать новую информацию.</param>
		protected override void OnRegisterPortfolio(Portfolio portfolio)
		{
			GetPortfolio(portfolio.Name,
			             name =>
			             {
				            _initialMoney.Add(portfolio, 0);

				             portfolio.Trader = this;
				             return portfolio;
			             },
			             p1 => false);
		}

		/// <summary>
		/// Начать получать новую информацию (например, <see cref="Security.LastTrade"/> или <see cref="Security.BestBid"/>) по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать новую информацию.</param>
		protected override void OnRegisterSecurity(Security security)
		{
			GetSecurity(security.Id,
			            id =>
			            {
				            security.Trader = this;
				            return security;
			            },
			            s1 => false);
		}

		private void DisposeEmulator()
		{
			_marketEmulator.NewOrder -= OnNewOrder;
			_marketEmulator.OrderChanged -= OnOrderChanged;
			_marketEmulator.OrderFailed -= OnOrderFailed;
			_marketEmulator.NewMyTrades -= OnNewMyTrades;
			_marketEmulator.ProcessDataError -= RaiseProcessDataError;
			_marketEmulator.NewTrades -= OnNewTrades;
			_marketEmulator.MarketDepthsChanged -= OnDepthsChanged;
			_marketEmulator.MarketTimeChanged -= OnMarketTimeChanged;
			_marketEmulator.NewOrderLogItems -= OnOrderLogItems;
			_marketEmulator.MessageReceived -= OnEmulatorRaiseEvents;

			MarketEmulator.Dispose();
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			if (State == EmulationStates.Started || State == EmulationStates.Suspended)
				Stop();

			DisposeEmulator();

			base.DisposeManaged();
		}
	}
}
